package com.munisense.webservices.javaclient;

import java.io.IOException;
import java.util.List;

import javax.xml.bind.JAXBException;

import org.xml.sax.SAXException;

import com.munisense.webservices.javaclient.exceptions.MuniWebserviceException;
import org.apache.commons.lang.StringUtils;

/**
 * GenericService
 * 
 * This is the generic service abstraction that allows a client to do basic
 * fetching of lists, fetching of single items and updating of single items.
 * More options will be added later.
 * 
 * Widely used objects will get their own service implementation, but every
 * object is also accessible via the generic service.
 * 
 * TODO Request method is a string here, although an ENUM might be more fitting.
 * TODO Create class that can parse lists of filters and fields
 */
public class GenericService {

	/**
	 * Service Configuration
	 */
	private ServiceConf serviceConf;
	private static final int HTTP_STATUS_OK = 200;

	/**
	 * Constructor that couples the service configuration to the service.
	 * 
	 * @param serviceConf
	 */
	public GenericService(ServiceConf serviceConf) {
		this.serviceConf = serviceConf;
	}

	/**
	 * Helper function to do a webservice call. This can be used if any of the
	 * other methods are not fitting.
	 * 
	 * @param className
	 *            Name of the class
	 * @param resourcePath
	 *            Additional resourcepath
	 * @param method
	 *            HTTP Request method (GET/SET/PUT/DELETE)
	 * @param data
	 *            Additional data to send to the webservice
	 * @param fieldsOverride
	 *            Alternative list of fields to fetch
	 * @param filter
	 *            Filter
	 * @return Object of type as provided by argument className
	 * @throws IOException
	 * @throws SAXException
	 * @throws JAXBException
	 */
	public <T> T request(Class<T> className, String resourcePath, String method, T data, List<String> fieldsOverride, List<MuniFilter> filter) throws IOException, SAXException, JAXBException {

		MuniHTTPClient<T> httpClient = new MuniHTTPClient<T>(className, serviceConf.isAllowUnverifiedHost(), serviceConf.isEnableLogging());

		// Allows the resource path to be expanded with data from the
		// fieldsOverride and filter
		String resourcePathExpanded = this.expandResourcePath(resourcePath, fieldsOverride, filter);

		MuniHTTPResponse<T> response = httpClient.request(serviceConf.getHost(), resourcePathExpanded, serviceConf.getUser(), serviceConf.getPassword(), method, data, className);

		if (response.getHttpStatus() != HTTP_STATUS_OK) {
			throw new MuniWebserviceException(response.getHttpStatus(), response.getHttpError());
		}

		T responseObject = response.getData();

		return responseObject;
	}

	/**
	 * Helper function to add the fieldsOverride and filter to the 
	 * resource path.
	 * 
	 * @param resourcePath
	 * @param fieldsOverride
	 * @param filter
	 * @return Expanded resource path with fieldsOverride and filter attached
	 */
	private String expandResourcePath(String resourcePath, List<String> fieldsOverride, List<MuniFilter> filter) {
		String resourcePathExpanded = resourcePath;

		// Add field override to the resource path
		if (fieldsOverride != null) {
			resourcePathExpanded += (resourcePathExpanded.contains("?") ? "&" : "?") + "$fields=" + StringUtils.join(fieldsOverride, ',');
		}

		// Add filter to the resource path
		if (filter != null) {
			resourcePathExpanded += (resourcePathExpanded.contains("?") ? "&" : "?") + "$filter=" + StringUtils.join(filter, ',');
		}

		return resourcePathExpanded;
	}

	/**
	 * Method to fetch a list of objects from the webservice.
	 * 
	 * @param className
	 *            Name of the class (For instance: DOIndoorClimateList)
	 * @param controllerName
	 *            Name of the controller (For instance: DOIndoorClimate)
	 * @param fieldsOverride
	 *            Alternative list of fields to fetch
	 * @param filter
	 *            Filter
	 * @return List Object of type as provided by argument className
	 * @throws IOException
	 * @throws SAXException
	 * @throws JAXBException
	 */
	public <T> T getList(Class<T> className, String controllerName, List<String> fieldsOverride, List<MuniFilter> filter) throws IOException, SAXException, JAXBException {
		return this.request(className, controllerName, "GET", null, fieldsOverride, filter);
	}

	/**
	 * Method to update an object
	 * 
	 * This usually done by fetching the object first. Then modify some of its
	 * properties and post the modified object using this method.
	 * 
	 * @param className
	 *            Name of the class (For instance: DOIndoorClimateList)
	 * @param controllerName
	 *            Name of the controller (For instance: DOIndoorClimate)
	 * @param object
	 *            The instance of the object to update
	 * @param id
	 *            The id of the object to update
	 * @return The response data object, might be empty
	 * @throws IOException
	 * @throws SAXException
	 * @throws JAXBException
	 */
	public <T> T update(Class<T> className, String controllerName, T object, int id) throws IOException, SAXException, JAXBException {
		return this.request(className, controllerName + "/" + id, "PUT", object, null, null);
	}

	/**
	 * Method to fetch an object by ID
	 * 
	 * @param className
	 *            Name of the class (For instance: DOIndoorClimateList)
	 * @param controllerName
	 *            Name of the controller (For instance: DOIndoorClimate)
	 * @param id
	 *            The id of the object to fetch
	 * @param fieldsOverride
	 *            Alternative list of fields to fetch
	 * @return The requested object (if found)
	 * @throws IOException
	 * @throws SAXException
	 * @throws JAXBException
	 */
	public <T> T get(Class<T> className, String controllerName, int id, List<String> fieldsOverride) throws SAXException, JAXBException, IOException {
		return this.request(className, controllerName + "/" + id, "GET", null, fieldsOverride, null);
	}

	/**
	 * Method to fetch an list of results
	 * 
	 * Returns a list of results from an objects property
	 * 
	 * @param className
	 *            Name of the class (For instance: DOIndoorClimateList)
	 * @param controllerName
	 *            Name of the controller (For instance: DOIndoorClimate)
	 * @param id
	 *            The id of the object to fetch
	 * @param property
	 *            Property to query
	 * @param options
	 *            Object containing the query options (May be null)
	 * @return The requested object (if found)
	 * @throws IOException
	 * @throws SAXException
	 * @throws JAXBException
	 */
	public <T> T propertyQuery(Class<T> className, String controllerName, int id, String property, ResultQueryOptions options) throws SAXException, JAXBException, IOException {
		
		String optionString = "";
		if(options != null)
		  optionString = options.generateQueryString();
		
		return this.request(className, controllerName + "/" + id + "/" + property + "/query" + (optionString != "" ? "?" + optionString : ""), "GET", null, null, null);
	}
}
